package com.yuneec.fly.utils;

import android.graphics.Point;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by jw.gu on 2016/6/16.
 */
public class MathUtils {

    public static class LatLong {
        private double latitude;
        private double longitude;

        public LatLong(double latitude, double longitude) {
            this.latitude = latitude;
            this.longitude = longitude;
        }

        public double getLatitude() {
            return latitude;
        }

        public double getLongitude() {
            return longitude;
        }
    }


    public static List<Point> simplifyPoints(List<Point> list) {
        return simplifyPoints(list, 10);
    }

    /**
     * Based on the Ramer–Douglas–Peucker algorithm
     * http://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm
     *
     * @param list    List of xPX/yPX points in the curve.
     * @param epsilon Tolerance for determining list of points for approximation of curve.
     * @return List of xPX/yPX points in the approximated curve.
     */
    public static List<Point> simplifyPoints(List<Point> list, double epsilon) {
        int index = 0;
        double dmax = 0;
        int lastIndex = list.size() - 1;

        // Find the point with the maximum distance.
        for (int i = 1; i < lastIndex; i++) {
            double d = pointDistance(list.get(0), list.get(lastIndex), list.get(i));
            if (d > dmax) {
                index = i;
                dmax = d;
            }
        }

        // If max distance is greater than epsilon, recursively simplify.
        List<Point> ResultList = new ArrayList<Point>();
        if (dmax > epsilon) {
            // Recursive call.
            List<Point> recResults1 = simplifyPoints(list.subList(0, index + 1), epsilon);
            List<Point> recResults2 = simplifyPoints(list.subList(index, lastIndex + 1), epsilon);

            // Build the result list.
            recResults1.remove(recResults1.size() - 1);
            ResultList.addAll(recResults1);
            ResultList.addAll(recResults2);
        } else {
            ResultList.add(list.get(0));
            ResultList.add(list.get(lastIndex));
        }

        return ResultList;
    }

    public static List<LatLong> simplify(List<LatLong> list) {
        return simplify(list, 0.0001);
    }

    /**
     * Based on the Ramer–Douglas–Peucker algorithm
     * http://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm
     *
     * @param list    List of lat/long points in the curve.
     * @param epsilon Tolerance for determining list of points for approximation of curve.
     * @return List of lat/long points in the approximated curve.
     */
    public static List<LatLong> simplify(List<LatLong> list, double epsilon) {
        int index = 0;
        double dmax = 0;
        int lastIndex = list.size() - 1;

        // Find the point with the maximum distance.
        for (int i = 1; i < lastIndex; i++) {
            double d = pointToLineDistance(list.get(0), list.get(lastIndex), list.get(i));
            if (d > dmax) {
                index = i;
                dmax = d;
            }
        }

        // If max distance is greater than epsilon, recursively simplify.
        List<LatLong> ResultList = new ArrayList<LatLong>();
        if (dmax > epsilon) {
            // Recursive call.
            List<LatLong> recResults1 = simplify(list.subList(0, index + 1), epsilon);
            List<LatLong> recResults2 = simplify(list.subList(index, lastIndex + 1), epsilon);

            // Build the result list.
            recResults1.remove(recResults1.size() - 1);
            ResultList.addAll(recResults1);
            ResultList.addAll(recResults2);
        } else {
            ResultList.add(list.get(0));
            ResultList.add(list.get(lastIndex));
        }

        return ResultList;
    }

    /**
     * Provides the distance from a point P to the line segment that passes
     * through A-B. If the point is not on the side of the line, returns the
     * distance to the closest point
     *
     * @param L1 First point of the line
     * @param L2 Second point of the line
     * @param P  Point to measure the distance
     * @return distance between point and line in meters.
     */
    public static double pointDistance(Point L1, Point L2, Point P) {
        int A = P.x - L1.x;
        int B = P.y - L1.y;
        int C = L2.x - L1.x;
        int D = L2.y - L1.y;

        double dot = A * C + B * D;
        double len_sq = C * C + D * D;
        double param = dot / len_sq;

        double xx, yy;

        if (param < 0) // point behind the segment
        {
            xx = L1.x;
            yy = L1.y;
        } else if (param > 1) // point after the segment
        {
            xx = L2.x;
            yy = L2.y;
        } else { // point on the side of the segment
            xx = L1.x + param * C;
            yy = L1.y + param * D;
        }

        return Math.hypot(xx - P.x, yy - P.y);
    }


    /**
     * Provides the distance from a point P to the line segment that passes
     * through A-B. If the point is not on the side of the line, returns the
     * distance to the closest point
     *
     * @param L1 First point of the line
     * @param L2 Second point of the line
     * @param P  Point to measure the distance
     * @return distance between point and line in meters.
     */
    public static double pointToLineDistance(LatLong L1, LatLong L2, LatLong P) {
        double A = P.getLatitude() - L1.getLatitude();
        double B = P.getLongitude() - L1.getLongitude();
        double C = L2.getLatitude() - L1.getLatitude();
        double D = L2.getLongitude() - L1.getLongitude();

        double dot = A * C + B * D;
        double len_sq = C * C + D * D;
        double param = dot / len_sq;

        double xx, yy;

        if (param < 0) // point behind the segment
        {
            xx = L1.getLatitude();
            yy = L1.getLongitude();
        } else if (param > 1) // point after the segment
        {
            xx = L2.getLatitude();
            yy = L2.getLongitude();
        } else { // point on the side of the segment
            xx = L1.getLatitude() + param * C;
            yy = L1.getLongitude() + param * D;
        }

        return Math.hypot(xx - P.getLatitude(), yy - P.getLongitude());
    }
}
